﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using System.Web.Http;
using Newtonsoft.Json;

namespace Sustainalytics.UtilsTests
{
    static public class TestUtils
    {

        public static bool StreamsAreIdentical(Stream a, Stream b)
        {
            if (a.Length != b.Length)
                return false;

            a.Position = 0;
            b.Position = 0;

            var buffer1 = new byte[65535];
            var buffer2 = new byte[65535];

            for (var i = 0; i < a.Length; i++)
            {
                var found1 = a.Read(buffer1, 0, 65535);
                var found2 = b.Read(buffer2, 0, 65535);

                if (found1 != found2)
                    return false;

                for (var j = 0; j < found1; j++)
                    if (buffer1[j] != buffer2[j])
                        return false;
            }
            return true;
        }
        public static T setupController<T>(T newcontroller) where T:ApiController
        {
            newcontroller.Request = new HttpRequestMessage();
            newcontroller.Configuration = new HttpConfiguration();

            return newcontroller;
        }

        public static T setupControllerWithFile<T>(T newcontroller, string contentType, string name, Stream inputStream) where T : ApiController
        {
            newcontroller.Request = new HttpRequestMessage();
            newcontroller.Configuration = new HttpConfiguration();

            newcontroller.Request.Content = new System.Net.Http.StreamContent(inputStream);
            newcontroller.Request.Content.Headers.Add("Content-Type", contentType);
            newcontroller.Request.Content.Headers.Add("Content-Disposition", "attachment; filename=" + name);

            return newcontroller;
        }

        static public bool AreExpected<T>(this Task<HttpResponseMessage> taskresponse, HttpStatusCode expectedCode, T expecteObj)
        {
            var response = taskresponse.Result;
            return response.AreExpected<T>(expectedCode, expecteObj);
        }

        static public bool AreExpected<T>(this HttpResponseMessage response, HttpStatusCode expectedCode, T expecteObj)
        {
            var indent = new JsonSerializerSettings() { Formatting = Formatting.Indented };
            if (expecteObj.IsAnonymousType())
            {
                object retObject = null;
                var hasResponse = response.TryGetContentValue<object>(out retObject);
                return (expectedCode == response.StatusCode) && hasResponse && (JsonConvert.SerializeObject(retObject, indent) == JsonConvert.SerializeObject(expecteObj, indent));
            }
            else
            {
                T retObject;
                var hasResponse = response.TryGetContentValue<T>(out retObject);
                
                return (expectedCode == response.StatusCode) && hasResponse && (JsonConvert.SerializeObject(retObject, indent) == JsonConvert.SerializeObject(expecteObj, indent));
            }
        }
        static public bool AreExpectedByProperties<T>(this HttpResponseMessage response, HttpStatusCode expectedCode, T expectedObj, params Expression<Func<T, object>>[] propertiesToCompare)
        {
            object retObject = null;
            var hasResponse = response.TryGetContentValue<object>(out retObject);
            return expectedCode == response.StatusCode && hasResponse &&
                Compare(retObject, expectedObj, GetPropertiesName(propertiesToCompare).ToArray());

        }
        static public bool AreExpectedByProperties<T>(this HttpResponseMessage response, T expectedObj, params Expression<Func<T,object>> [] propertiesToCompare)
        {
            object retObject = null;
            var hasResponse = response.TryGetContentValue<object>(out retObject);
            return hasResponse &&
                Compare(retObject, expectedObj, GetPropertiesName(propertiesToCompare).ToArray());

        }
      
        public static bool IsAnonymous(this Type type)
        {
            if (type.IsGenericType)
            {
                var d = type.GetGenericTypeDefinition();
                if (d.IsClass && d.IsSealed && d.Attributes.HasFlag(TypeAttributes.NotPublic))
                {
                    var attributes = d.GetCustomAttributes(typeof(CompilerGeneratedAttribute), false);
                    if (attributes != null && attributes.Length > 0)
                    {
                        //WOW! We have an anonymous type!!!
                        return true;
                    }
                }
            }
            return false;
        }

        public static bool IsAnonymousType<T>(this T instance)
        {
            return IsAnonymous(instance.GetType());
        }


        public static bool AreListsEqual<T>(IEnumerable<T> a, IEnumerable<T> b) where T: class
        {

            var arrA = a.ToArray();
            var arrB = b.ToArray();

            if (arrA.Length == arrB.Length && arrB.Length > 0)
            {
                for(var i = 0; i < arrB.Length; i++)
                {
                    if  (!arrA[i].Equals(arrB[i]))
                        return false;
                        
                }
                return true;
            }

            return false;
        }


        public static bool AreListsEqualByElementPublicProperty<T>(IEnumerable<T> a, IEnumerable<T> b) where T : class
        {

            var arrA = a.ToArray();
            var arrB = b.ToArray();

            if (arrA.Length == arrB.Length && arrB.Length > 0)
            {
                for (var i = 0; i < arrB.Length; i++)
                {
                    if (!AreEqualByPublicProperty(arrA[i], arrB[i], "failed in index " + i.ToString()))
                        return false;
                }
                return true;
            }

            return false;
        }


        public static  bool AreEqualByPublicProperty<T>(T a, T b, string msg, params string[] ignore)
        {
            if ((a != null && b == null) || (a == null && b != null))
            {
                throw new Microsoft.VisualStudio.TestTools.
                        UnitTesting.AssertFailedException("Null input: " + msg);

            }

            var aProps = a.GetType().GetProperties();
            var bProps = b.GetType().GetProperties();
            int count = aProps.Count();
            string aa, bb;
            for (int i = 0; i < count; i++)
            {
                aa = aProps[i].GetValue(a, null).ToStringNullSafe();
                bb = bProps[i].GetValue(b, null).ToStringNullSafe();
                if (aa != bb && !ignore.Any(x => x == aProps[i].Name))
                {
                    throw new Microsoft.VisualStudio.TestTools.
                        UnitTesting.AssertFailedException("properties dont match: " + aProps[i].Name + ", " + msg);
                }
            }
            return true;
        }

        public static string ToStringNullSafe(this object obj)
        {
            return obj != null ? obj.ToString() : String.Empty;
        }
        public static bool Compare<T>(T a, T b, params string[] ignore)
        {
            var aProps = a.GetType().GetProperties();
            var bProps = b.GetType().GetProperties();
            int count = aProps.Count();
            string aa, bb;
            for (int i = 0; i < count; i++)
            {
                aa = aProps[i].GetValue(a, null).ToStringNullSafe();
                bb = bProps[i].GetValue(b, null).ToStringNullSafe();
                if (aa != bb && !ignore.Any(x => x == aProps[i].Name))
                {
                    return false;
                }
            }
            return true;
        }

        public static bool Compare<T, U>(T a, U b, params string[] propertiesToCompare )
        {
            var aProps = a.GetType().GetProperties();
            var bProps = b.GetType().GetProperties();
            
            if (propertiesToCompare == null || propertiesToCompare.Length == 0)
                return false;
            
            foreach (var name in propertiesToCompare)
            {
                var aProp = aProps.FirstOrDefault(p => p.Name == name);
                var bProp = bProps.FirstOrDefault(p => p.Name == name);

                if (aProp == null || bProp == null)
                    return false;
                
                var aa = aProp.GetValue(a, null).ToStringNullSafe();
                var bb = bProp.GetValue(b, null).ToStringNullSafe();
                
                if (aa != bb)
                {
                    return false;
                }
            }
            return true;
        }

        private static List<string> GetPropertiesName(LambdaExpression[] propertiesToCompare)
        {
            List<string> names = new List<string>();
            if(propertiesToCompare == null)
            foreach (var expression in propertiesToCompare)
            {
                names.Add(((MemberExpression)expression.Body).Member.Name);
            }

            return names;
        }
        
    }    
}
